﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using DocumentFormat.OpenXml;
using System.Text.RegularExpressions;
using intelliEssay.Core.DocumentStructure;

namespace intelliEssay.Core.DocumentStructure
{
    /// <summary>
    /// This class is used to hold information for a Paragraph's numbering 
    /// </summary>
    public class ParagraphNumbering
    {
        private class NumberingInforamtion
        {
            public int Restart;
            public int Start;
            public int lvlId;
            public int abstractNumId;
            public int currentIdx;
            public int CompareTo(NumberingInforamtion other)
            {
                if (lvlId == other.lvlId && abstractNumId == other.abstractNumId)
                {
                    return 0;
                }
                return -1;
            }
            public new bool Equals(NumberingInforamtion obj)
            {
                return this.CompareTo(obj) == 0;
            }
            public override string ToString()
            {
                return "numInsId: " + abstractNumId + "    lvl: " + lvlId + "    Current: " + currentIdx;
            }
        }

        /// <summary>
        /// Gets a well iniialized Level, considering overrides
        /// </summary>
        /// <param name="numId">the referenced Index</param>
        /// <param name="lvl">the level index</param>
        /// <param name="abstractNumCollection">the AbstractNum Collection</param>
        /// <param name="instanceNumCollection">the InstanceNum collection</param>
        /// <param name="Style-Dict">the styleDictionary</param>
        /// <returns></returns>
        /// <remarks>This is intended majorly to use internally</remarks>
        public static Level GetResolvedLevel(int numId, int lvl, IEnumerable<AbstractNum> abstractNumCollection, IEnumerable<NumberingInstance> instanceNumCollection, Dictionary<string, Style> styleDict, out AbstractNum abstractNum, bool treatNumIdAsAbstractId = false)
        {
            abstractNum = null;
            Level level = _GetResolvedLevel(numId, lvl, abstractNumCollection, instanceNumCollection, out abstractNum, treatNumIdAsAbstractId);
            if (level != null)
            {
                return level;
            }
            if (abstractNum != null)
                if (abstractNum.NumberingStyleLink != null)
                {
                    string styleLink = abstractNum.NumberingStyleLink.Val;
                    Style current = styleDict[styleLink];
                    if (current.StyleParagraphProperties != null)
                    {
                        if (current.StyleParagraphProperties.NumberingProperties != null)
                        {
                            var numpPr = current.StyleParagraphProperties.NumberingProperties;
                            if (numpPr.NumberingId != null)
                            {
                                int _numId = numpPr.NumberingId.Val;
                                int _lvl = 0;
                                if (numpPr.NumberingLevelReference != null)
                                {
                                    _lvl = numpPr.NumberingLevelReference.Val;
                                }
                                level = _GetResolvedLevel(_numId, _lvl, abstractNumCollection, instanceNumCollection, out abstractNum, treatNumIdAsAbstractId);
                                if (level != null)
                                {
                                    return level;
                                }
                            }
                        }
                    }
                    while (current.BasedOn != null)
                    {
                        current = styleDict[current.BasedOn.Val];
                        if (current.StyleParagraphProperties != null)
                        {
                            if (current.StyleParagraphProperties.NumberingProperties != null)
                            {
                                var numpPr = current.StyleParagraphProperties.NumberingProperties;
                                if (numpPr.NumberingId != null)
                                {
                                    int _numId = numpPr.NumberingId.Val;
                                    int _lvl = 0;
                                    if (numpPr.NumberingLevelReference != null)
                                    {
                                        _lvl = numpPr.NumberingLevelReference.Val;
                                    }
                                    level = _GetResolvedLevel(_numId, _lvl, abstractNumCollection, instanceNumCollection, out abstractNum, treatNumIdAsAbstractId);
                                    if (level != null)
                                    {
                                        return level;
                                    }
                                }
                            }
                        }
                    }
                }
            return null;
        }
        private static Level _GetResolvedLevel(int numId, int lvl, IEnumerable<AbstractNum> abstractNumCollection, IEnumerable<NumberingInstance> instanceNumCollection, out AbstractNum abstractNum, bool treatNumIdAsAbstractId = false)
        {
            abstractNum = null;
            Level Result = null;
            int abstNumId = numId;
            LevelOverride levelOverrideForStartUps = null;
            if (treatNumIdAsAbstractId)
            {
                abstractNum = abstractNumCollection.FirstOrDefault<AbstractNum>(o =>
                    {
                        return o.AbstractNumberId == abstNumId;
                    });
            }
            else
            {
                NumberingInstance numIns = instanceNumCollection.FirstOrDefault<NumberingInstance>(o =>
                    {
                        return o.NumberID == numId;
                    });
                if (numIns == null)
                {
                    return null;
                }
                if (numIns.AbstractNumId == null)
                {
                    return null;
                }
                abstNumId = numIns.AbstractNumId.Val;
                abstractNum = abstractNumCollection.FirstOrDefault<AbstractNum>(o =>
                {
                    return o.AbstractNumberId == abstNumId;
                });
                LevelOverride levelOverride = numIns.ChildElements.FirstOrDefault<OpenXmlElement>(o =>
                    {
                        if (o is LevelOverride)
                        {
                            LevelOverride lo = o as LevelOverride;
                            if (lo.LevelIndex != null)
                            {
                                if (lo.LevelIndex == lvl)
                                {
                                    if (lo.Level != null)
                                    {
                                        return true;
                                    }
                                }
                            }
                        }
                        return false;
                    }) as LevelOverride;
                if (levelOverride != null)
                {
                    Result = levelOverride.Level;
                }
                levelOverrideForStartUps = numIns.ChildElements.FirstOrDefault<OpenXmlElement>(o =>
                 {
                     if (o is LevelOverride)
                     {
                         LevelOverride lo = o as LevelOverride;
                         if (lo.LevelIndex != null)
                         {
                             if (lo.LevelIndex == lvl)
                             {
                                 if (lo.Elements<StartOverrideNumberingValue>().Count() != 0)
                                 {
                                     return true;
                                 }
                             }
                         }
                     }
                     return false;
                 }) as LevelOverride;
                if (Result != null)
                {
                    if (levelOverrideForStartUps != null)
                    {
                        Result.StartNumberingValue.Val = (levelOverrideForStartUps.FirstChild as StartOverrideNumberingValue).Val;
                    }
                    Result.StartNumberingValue = Result.StartNumberingValue == null ? new StartNumberingValue() { Val = 1 } : Result.StartNumberingValue;
                    Result.LevelRestart = Result.LevelRestart == null ? new LevelRestart() { Val = -1 } : Result.LevelRestart;
                    return Result;
                }
            }
            Result = abstractNum.ChildElements.FirstOrDefault<OpenXmlElement>(o =>
                {
                    if (o is Level)
                    {
                        return (o as Level).LevelIndex == lvl;
                    }
                    return false;
                }) as Level;
            if (!treatNumIdAsAbstractId)
                if (levelOverrideForStartUps != null)
                {
                    Result.StartNumberingValue.Val = (levelOverrideForStartUps.Elements<StartOverrideNumberingValue>().First()).Val;
                }
            if (Result == null)
            {
                return null;
            }
            Result.StartNumberingValue = Result.StartNumberingValue == null ? new StartNumberingValue() { Val = 1 } : Result.StartNumberingValue;
            Result.LevelRestart = Result.LevelRestart == null ? new LevelRestart() { Val = -1 } : Result.LevelRestart;
            Result.NumberingFormat = Result.NumberingFormat == null ? new NumberingFormat() : Result.NumberingFormat;
            return Result;
        }
        internal Dictionary<Paragraph, string> ParagraphNumberingLevelText = new Dictionary<Paragraph, string>();
        internal Dictionary<int, Dictionary<int, int>> AbstractNumId_LvlId_CurrentIdx = new Dictionary<int, Dictionary<int, int>>();
        internal Dictionary<Paragraph, string> ParagraphNumberingDecimalFmt = new Dictionary<Paragraph, string>();
        internal Dictionary<Paragraph, NumberFormatValues> ParagraphNumberingFmtValues = new Dictionary<Paragraph, NumberFormatValues>();
        private List<NumberingInforamtion> numberingInformation = new List<NumberingInforamtion>();
        Dictionary<int, int> abstractLastLevelRefernence = new Dictionary<int, int>();
        /// <summary>
        /// Gets the paragraph's level, if any, infered by its numbering text format.
        /// </summary>
        /// <param name="p">paragrpah to be checked for numbering lvl txt</param>
        /// <returns>the lvl of the paragraph</returns>
        /// <exception cref="ApplicationException"/>
        public int GetTitleParagraphLevelInferedByNumbering(Paragraph p)
        {
            try
            {
                string lvlTxt = ParagraphNumberingLevelText[p];
                return lvlTxt.Split('%').Length - 1;
            }
            catch
            {
            }
            throw new ApplicationException("This paragraph does not contain numbering inforamtion");
        }
        /// <summary>
        /// Gets the paragraph's level text, if any.
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        /// <exception cref="ApplicationException"/>
        public string GetParagraphLevelText(Paragraph p)
        {
            try
            {
                return ParagraphNumberingLevelText[p];
            }
            catch
            {
            }
            throw new ApplicationException("This paragraph does not contain numbering inforamtion");
        }
        /// <summary>
        /// Gets the paragraph's accumulated level text in decimal format.
        /// <para>Note this format is UNFORMATED for the level type, ie, if the levelType is specified </para>
        /// <para>in Roman ,or upperLetter, the decimal format is returned. The following is an example.</para>
        /// <example>
        /// Suppose a paragraph whose numbering looks like I.II.III in word, if queried using this method,
        /// 1.2.3 is returned.
        /// </example>
        /// </summary>
        /// <param name="p">paragraph to be checked</param>
        /// <returns>the string unformated text</returns>
        /// <exception cref="ApplicationException"/>
        public string GetParagraphLevelStringUnformated(Paragraph p)
        {
            try
            {
                return ParagraphNumberingDecimalFmt[p];
            }
            catch
            {
                throw new ApplicationException("This paragraph does not have a numbering property");
            }
        }
        /// <summary>
        /// Outputs the NumberFormat for a paragraph's numbering property
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public NumberFormatValues GetParagraphFormatValue(Paragraph p)
        {
            return ParagraphNumberingFmtValues[p];
        }
        internal void AdjustNumberingInformation(int abstractId, int lvl, string lvlText, int startVal, int restartVal, IEnumerable<AbstractNum> abstractNumCollection, IEnumerable<NumberingInstance> numInsCollection, Paragraph para, Dictionary<string, Style> styleDict)
        {
            //  NumberingInforamtion firstAdd = null;
            NumberingInforamtion numInformation;
            numInformation = numberingInformation.FirstOrDefault<NumberingInforamtion>(o =>
            {
                return o.lvlId == lvl && o.abstractNumId == abstractId;
            });
            if (numInformation == null)
            {
                numInformation = new NumberingInforamtion()
                 {
                     abstractNumId = abstractId,
                     lvlId = lvl,
                     currentIdx = startVal - 1,
                     Start = startVal,
                     Restart = restartVal
                 };
                //   firstAdd = numInformation;
                numberingInformation.Add(numInformation);
            }
            if (abstractLastLevelRefernence.ContainsKey(abstractId))
                if (abstractLastLevelRefernence[abstractId] > lvl)
                {
                    foreach (NumberingInforamtion numInfo in numberingInformation)
                    {
                        if (numInfo.abstractNumId != abstractId)
                        {
                            continue;
                        }
                        if (numInfo.Restart == -1 && numInfo.lvlId > lvl)
                        {
                            numInfo.currentIdx = numInfo.Start - 1;
                        }
                        else if (numInfo.Restart > lvl)
                        {
                            numInfo.currentIdx = numInfo.Start - 1;
                        }
                    }
                }
            MatchCollection mc = reg.Matches(lvlText);
            StringBuilder sb = new StringBuilder();
            int maxLvl = -1;
            List<NumberingInforamtion> ReferencedLevel = new List<NumberingInforamtion>();
            foreach (Match m in mc)
            {
                if (m.Groups[1].Index == 0)
                {
                    sb.Append(m.Groups[1].Value + ".");
                }
                else
                {
                    int PrevIdx = m.Groups[1].Index - 1;
                    if (lvlText[PrevIdx] == '%')
                    {
                        NumberingInforamtion referenced = numberingInformation.FirstOrDefault<NumberingInforamtion>(o =>
                            {
                                return o.lvlId == int.Parse(m.Groups[1].Value[0].ToString()) - 1 && o.abstractNumId == abstractId;
                            });
                        if (referenced == null)
                        {
                            AbstractNum abstractNum;
                            Level referencedLevel = GetResolvedLevel(abstractId, int.Parse(m.Groups[1].Value[0].ToString()) - 1, abstractNumCollection, numInsCollection, styleDict, out abstractNum, true);
                            referenced = new NumberingInforamtion()
                            {
                                lvlId = referencedLevel.LevelIndex,
                                abstractNumId = abstractId,
                                Start = referencedLevel.StartNumberingValue.Val,
                                currentIdx = referencedLevel.StartNumberingValue.Val,
                                Restart = referencedLevel.LevelRestart.Val
                            };
                            numberingInformation.Add(referenced);
                        }
                        if (referenced.lvlId == lvl)
                        {
                            referenced.currentIdx++;
                        }
                        if (referenced.currentIdx == 0)
                        {
                            referenced.currentIdx = referenced.Start;
                        }
                        if (m.Value.Length != 1)
                        {
                            sb.Append(referenced.currentIdx.ToString() + m.Value.Substring(1, m.Value.Length - 1) + ".");
                        }
                        else
                        {
                            sb.Append(referenced.currentIdx.ToString() + ".");
                        }
                        ReferencedLevel.Add(referenced);

                        if (maxLvl < int.Parse(m.Groups[1].Value[0].ToString()) - 1)
                        {
                            maxLvl = int.Parse(m.Groups[1].Value[0].ToString()) - 1;
                        }
                    }
                    else
                    {
                        sb.Append(m.Groups[1].Value + ".");
                    }
                }
            }
            ParagraphNumberingDecimalFmt[para] = sb.ToString();
            try
            {
                abstractLastLevelRefernence[abstractId] = maxLvl;
            }
            catch
            {
                abstractLastLevelRefernence.Add(abstractId, maxLvl);
            }
        }
        private Regex reg = new Regex("(\\d+)", RegexOptions.Compiled);
    }

}
